/* Copyright Andy Mitchell 2007. No warranties */

function Thread(threadList, id, parseType, parseArray){
	this.id = id;
	this.threadList = threadList;
	this.body = {};
	this.labels = [];
	this.names = [];
	this.namesSenders = [];
	this.subject = "";
	this.rowDateHTML = "";
	this.rowSnippetHTML = "";
	this.unread = false;
	
	this.syncedDbSinceUpdate = true;
		
	if( parseType && parseArray ) this.update(parseType, parseArray);
};

Thread.prototype.checkAutoLabel = function(){
	// Find labels in body that are not already on the thread; and never auto-added before
	// Add them to the auto-added db table
	// Find associated rows, and pass them to the create labels
try{
	var newLabels = this.getBodyLabels(true);
	if( newLabels && newLabels.length > 0 ){

				
		var view = this.threadList.gtdinboxPage.view();
		if( view=="tl" ){
			var row = this.threadList.gtdinboxPage._threadListUI.getRowByThread(this);
			if( row ){
				var elCheck = row.elCheck;
				
				this.threadList.gtdinboxPage._lightbox.setType("autolabelling");
				this.threadList.gtdinboxPage._lightbox.active(true);
				
				var autoLabeller = {i:0};
				autoLabeller.checked = this.threadList.gtdinboxPage._threadListUI.checkRows();
				for( var i = 0; i < newLabels.length; i++ ){
					var request = {};
					request.labelname = newLabels[i][0];
					request.threadid = this.id;
					request.fPreAdd = GIPROTOTYPE.bind(this, function(){
							try{
							request.checked = this.threadList.gtdinboxPage._threadListUI.checkRows();
							this.threadList.gtdinboxPage._threadListUI.checkRows([]);
							this.threadList.gtdinboxPage._threadListUI.checkRow( elCheck, true);
							
							}catch(e){
								if( GTDDEBUGMODE ) alert("thread checkautolabel preadd\n" + e.toString());
							}
						});
					request.fCallback = GIPROTOTYPE.bind(this, function(){
							try{
							if( --autoLabeller.i==0 ){	// Finished
								this.threadList.gtdinboxPage._threadListUI.checkRows(autoLabeller.checked);
								this.threadList.gtdinboxPage._lightbox.active(false);
							}
							}catch(e){
								if( GTDDEBUGMODE ) alert("thread checkautolabel callback\n" + e.toString());
							}
						});
					request.threads = this;
					autoLabeller.i++;
					this.threadList.gtdinboxPage._labels.labelThread(request);
					this.threadList.gtdinboxPage.sp.addAutoLabelled(newLabels[i][1], request.labelname);	// (add msgid, label to db)
					this.threadList.gtdinboxPage.sp.addThreadLabel(this.id, request.labelname);
					this.syncDb();

				}
			}
		}else if( view=="cv" ){
			
		}
	}
}catch(e){
	if( GTDDEBUGMODE ) alert("thread checkautolabel\n" + e.toString());
}
};

Thread.prototype.update = function(parseType, a){
	switch(parseType){
		case "tb":
				if( a[10] ) this.rowSnippetHTML = a[10];
				if( a[14] ) this.rowDateHTML = a[14];
				if( !this.subject && a[9] ){
					this.subject = GIPROTOTYPE.decodeEntities(a[9].replace(/<.+?>/g, ""));
					this.subject = this.subject.replace(/\s{2,}/g, " ");	// in html form; spaces would be stripped
				}
				//if( /^\<b\>/.test(a[9]) ) this.unread = true;
				if( a[7] ) this.rowNamesHTML = a[7];
				
				if( this.labels.length==0 && a[5] ){
					for( var i = 0; i < a[5].length; i++ ){
						if( !/^\^/.test(a[5][i]) ) this.labels.push(a[5][i]);
					}
				}				
			break;
		case "ms":
				if( this.labels.length==0 && a[9] ){
					for( var i = 0; i < a[9].length; i++ ){
						if( !/^\^/.test(a[9][i]) ) this.labels.push(a[9][i]);
					}
				}
				if( a[13] && a[13][6] ){
					if( this.body[a[1]] ){
						this.body[a[1]].msg = a[13][6];
					}else{
						var recipients = null;
						if( a[13][9] && a[13][9][1] && a[13][9][1].length>0 ){
							recipients = GIPROTOTYPE.clone(a[13][9][1], "array");
							for( var i = 0; i < recipients.length; i++ ){
								recipients[i][1] = GIPROTOTYPE.realEmail(recipients[i][1]);
								this.names.push(recipients[i]);
							}
						}else if(a[13][1]){		// in format andy@productivefirefox.com or andy <andy@gtdinbox.com>
							recipients = GIPROTOTYPE.clone(a[13][1], "array");
							for( var i = 0; i < recipients.length; i++ ){
								if( /<.+?\@.+\..+?>/.test(recipients[i]) ){
									var match = recipients[i].match(/(.+?)\s<(.+)>/);
									recipients[i] = [match[1],match[2]];
								}else{
									recipients[i] = ["", recipients[i]];
								}
								recipients[i][1] = GIPROTOTYPE.realEmail(recipients[i][1]);
								this.names.push(recipients[i]);
							}
						}
						var sender = null;
						if( a[6] ){
							sender = [a[5],GIPROTOTYPE.realEmail(a[6])];
							this.names.push(sender);
							this.namesSenders.push(sender);
						}
						this.body[a[1]] = {epoch:a[7], 'sender':sender, 'recipients':recipients, 'msg':a[13][6]};
						this.syncedDbSinceUpdate = false;
					}
					if( this.eventHandlers ) this.eventHandlers.broadcast("update-body");
				}
				if( a[6] ){
					var n = [a[5],GIPROTOTYPE.realEmail(a[6])];
					this.names.push( n );	// name, email of sender
					this.namesSenders.push(n);
					this.syncedDbSinceUpdate = false;
				}
				if( !this.subject && a[12] ){
					this.subject = GIPROTOTYPE.decodeEntities(a[12].replace(/<.+?>/g, ""));
					this.subject = this.subject.replace(/\s{2,}/g, " ");	// in html form; spaces would be stripped
				}
			break;
	}	
};

Thread.prototype.initEventHandler = function(){
	if( !this.eventHandlers ) this.eventHandlers = new EventHandlers_Mutator(this);
};

Thread.prototype.getNames = function(sendersOnly){
	// Safely get names (unique, minus page owner)
	
	var environment = this.threadList.gtdinboxPage._environment;
	var names = [];
	var seen = {};
	var ignored = [];
	var threadNames = sendersOnly? this.namesSenders : this.names;
	for( var i = 0; i < threadNames.length; i++ ){
		if( !threadNames[i][0] ) threadNames[i][0] = GIPROTOTYPE.realEmail(threadNames[i][1]).replace(/@.*$/, "");
		if( !seen[threadNames[i][1]] ){
			if( environment.emails[threadNames[i][1]] ){
				ignored.push(threadNames[i]);
			}else{
				names.push(threadNames[i]);
			}
			seen[threadNames[i][1]] = true;
		}
	}
	
	if( names.length==0 ){	// probably a 'me' conversation (&& ignored.length==1)
		return ignored;
	}else return names;
};

Thread.prototype.syncDb = function(){
	// Write recipients/senders to the db for this thread
try{
	var senders = [];
	var sendersHash = {};
	var recipients = [];
	var recipientsHash = {};
	var environment = this.threadList.gtdinboxPage._environment;
	var r = null;
	for( b in this.body ){
		if( this.body[b].sender && !sendersHash[this.body[b].sender[1]] && !environment.emails[this.body[b].sender[1]] ){
			senders.push( this.body[b].sender );
			sendersHash[this.body[b].sender[1]] = true;
		}
		var r = this.body[b].recipients;
		if( r ){
			for( var i = 0; i < r.length; i++ ){
				if( !environment.emails[r[i][1]] && !recipientsHash[r[i][1]] ){
					recipientsHash[r[i][1]] = true;
					recipients.push(r[i]);
				}
			}
		}
		var s = this.body[b].senders;
		if( s ){
			for( var i = 0; i < s.length; i++ ){
				if( !environment.emails[s[i][1]] && !sendersHash[s[i][1]] ){
					sendersHash[s[i][1]] = true;
					senders.push(s[i]);
				}
			}
		}
	}
	
	this.threadList.gtdinboxPage.sp.addThreadContacts(this.id, senders, recipients);
	
	this.syncedDbSinceUpdate = true;
}catch(e){
	if( GTDDEBUGMODE ) alert("thread syncdb\n" + e.toString());
}
};

Thread.prototype.addLabel = function(labelname, noRemovals, fAfterRemoveCallback){
	// Add label to thread data; in reaction to Gmail adding a label to a thread (nb it does not instruct gmail to add the label itself, although it does remove labels if existing status items)
try{
	this.threadList.gtdinboxPage.unsafeWindow.console.trace();

	for( var i = this.labels.length - 1; i>=0; i-- ) if( this.labels[i]==labelname ) return;	// Check doesn't exist
	
	// Check only one status (only if onlyOneStatus option; unless clicked finished, in which case wipe)
	var settings = this.threadList.gtdinboxPage.settings;
	var statusPrefix = settings.labels.types.Statuses.prefix;
	var clickedFinished = labelname==(statusPrefix + settings.labels.types.Statuses.defaults.Finished);
	var labelsToRemove = [];
	if( clickedFinished || (settings.labels.onlyOneStatus && labelname.indexOf(statusPrefix)==0) ){	// It's a status, there can only be one!
		for( var i = this.labels.length - 1; i>=0; i-- ){	// go backwards to remove most recent first
			if( this.labels[i].indexOf(statusPrefix)==0 && this.labels[i]!=labelname ) labelsToRemove.push(this.labels[i]);
		}
	}
	
	this.labels.push(labelname);
	
	var safelabel = labelname.replace(/<.+?>/g, "");
	var names = this.getNames();
	//var d = (new Date()).valueOf();
	this.threadList.gtdinboxPage.sp.addThreadLabel(this.id, labelname);
	this.syncDb();
	
	if( !this.threadList.gtdinboxPage._labels.hasLabel(labelname) ){	// Newly created label; fake create it
		this.threadList.gtdinboxPage._labels.addNewLabel(labelname);
	};
	
	if( this.eventHandlers ) this.eventHandlers.broadcast("addLabel");
	
	if( !noRemovals ){
		var labelled = {count:0};
		var fCallback = function(){
				if( --labelled.count==0 ){
					if( fAfterRemoveCallback ) fAfterRemoveCallback();
				}
			};
		for( var i = 0; i < labelsToRemove.length; i++ ){
			labelled.count++;
			this.threadList.gtdinboxPage._labels.labelThread({labelname:labelsToRemove[i], 'on':false, 'threads':this, 'fCallback':fCallback});
		}
	}
}catch(e){
	if( GTDDEBUGMODE ) alert("thread addLabel\n" + e.toString());
}
};
Thread.prototype.removeLabel = function(labelname){
try{
	var found = false;
	for( var i = this.labels.length - 1; i>=0; i-- ){
		if( this.labels[i]==labelname ){
			this.labels.splice(i,1);
			found = true;
		}
	}
	if( !found ) return;
	
	//var safelabel = labelname.replace(/<.+?>/g, "");
	//this.threadList.gtdinboxPage.db.execute("DELETE FROM project_label_activity WHERE label = \"" + safelabel + "\" AND thread = '" + this.id + "'");
	this.threadList.gtdinboxPage.sp.removeThreadLabel(this.id, labelname);
	
	if( this.eventHandlers ) this.eventHandlers.broadcast("removeLabel");
	
}catch(e){
	if( GTDDEBUGMODE ) alert("thread removeLabel\n" + e.toString());
}
}
Thread.prototype.hasLabel = function(labelname){
	for( var i = 0; i < this.labels.length; i++ ) if( this.labels[i]==labelname ) return true;
	else return false;
};
Thread.prototype.verifyLabels = function(labels, noRemovals){
try{
	
	var tLabelsHash = {};
	for( var i = 0; i < this.labels.length; i++ ) tLabelsHash[this.labels[i]] = true;
	
	for( var i = labels.length - 1; i>=0; i-- ){
		if( tLabelsHash[labels[i]] ) delete tLabelsHash[labels[i]];	// they match, fine
		else this.addLabel(labels[i], noRemovals)	// new label currently unknown
	}
	
	// tLabelsHash now only contains labels that should no longer exist
	for( l in tLabelsHash ){
		if( tLabelsHash.hasOwnProperty(l) ) this.removeLabel(l);
	}
	
	return this.labels;
}catch(e){
	if( GTDDEBUGMODE ) alert("thread verifyLabels\n" + e.toString());
}
};

Thread.prototype.latestSentByMe = function(){
try{
	var environment = this.threadList.gtdinboxPage._environment;
	return environment.emails[this.getLatestMsg().sender[1]];
}catch(e){
	if( GTDDEBUGMODE ) alert("thread sentbyme\n" + e.toString());
}
};
Thread.prototype.sentToSelf = function(body, environment){
	// Find all names associated with this msg body(s); and check each 'sender' is me, and at least one recipient is me
try{
	if( !body ){	// take all
		body = [];
		for( id in this.body ) body.push(this.body[id]);
	}
	if( !(body instanceof Array) ) body = [body];
		
	if( !environment ) environment = this.threadList.gtdinboxPage._environment;
	var found = body.length>0? true : false;
	for( var i = 0; i < body.length; i++ ){
		var b = body[i];
		var senders = b.sender? [b.sender] : [];
		var recipients = b.recipients? b.recipients : [];
		
	
		var foundSender = senders.length>0? true : false;
		for( var j = 0; j < senders.length; j++ ){
			if( !environment.emails[senders[j][1]] ) foundSender = false;
		}
		
		var foundOneRecipient = false;
		for( var j = 0; j < recipients.length; j++ ){
			if( environment.emails[recipients[j][1]] ){
				foundOneRecipient = true;
				break;
			}
		}
		if( !foundSender || !foundOneRecipient ){
			found = false;
			break;
		}
	}
	return found;
}catch(e){
	if( GTDDEBUGMODE ) alert("thread senttoself\n" + e.toString());
}
};
Thread.prototype.sentByMe = function(body, environment){
try{
	if( !body ){	// take all
		body = [];
		for( id in this.body ) body.push(this.body[id]);
	}
	if( !(body instanceof Array) ) body = [body];
		
	if( !environment ) environment = this.threadList.gtdinboxPage._environment;
	var found = body.length>0? true : false;
	for( var i = 0; i < body.length; i++ ){
		var b = body[i];
		var senders = b.sender? [b.sender] : [];
		var foundSender = senders.length>0? true : false;
		for( var j = 0; j < senders.length; j++ ){
			if( !environment.emails[senders[j][1]] ) foundSender = false;
		}
		if( !foundSender ){
			found = false;
			break;
		}
	}
	return found
}catch(e){
	if( GTDDEBUGMODE ) alert("thread sentbyme\n" + e.toString());
}	
};

Thread.prototype.getBodyLabels = function(verifyInDb){
	// Check each msg in the thread; extracting known labels not on thread. If Status label, ensure msg was sent to self. Then check haven't auto-labelled that msgid/label combo before (using db). 
try{
	var labels = this.threadList.gtdinboxPage._labels.getLabelsArray();
	var statusPrefix = this.threadList.gtdinboxPage.settings.labels.types.Statuses.prefix;
	var environment = this.threadList.gtdinboxPage._environment;
	
	var currentLabels = {};
	for( var i = 0; i < this.labels.length; i++ ) currentLabels[this.labels[i]] = true;
	
	var newlabels = {};	// {msgid} = [labels]
	var msgids = [];
		
	var foundLabel = false;
	for( id in this.body ){
		msgids.push(id);
		var body = this.body[id];
		var sentToSelf = -1;

		for( var i = labels.length - 1; i>=0; i-- ){
			if( !currentLabels[labels[i]] && body.msg.indexOf("[" + labels[i] + "]")>-1 ){	// Contains label
				if( labels[i].indexOf(statusPrefix)==0 ){	// Status - only accept if sent to self
					if( sentToSelf<0 ) sentToSelf = this.sentByMe(body, environment);	// use by me instead - for Sent Items
					if( sentToSelf ){
						if( !newlabels[id] ) newlabels[id] = [];
						newlabels[id].push(labels[i]);
						foundLabel = true;
					}
				}else{
					if( !newlabels[id] ) newlabels[id] = [];
					newlabels[id].push(labels[i]);
					foundLabel = true;
				}
			}
		}
	}
	
	if( verifyInDb && foundLabel ){
		/*
		var fRow = function(statement){
				var msgid = statement.getString(0);
				var label = statement.getString(1);
				for( var i = newlabels[msgid].length - 1; i>=0; i-- ){
					if( newlabels[msgid][i]==label ) newlabels[msgid].splice(i, 1);
				}
			};
		var where = "";
		for( var i = 0; i < msgids.length; i++ ) where += (where.length>0? " OR ":"") + "msgid = '" + msgids[i] + "'";
		this.threadList.gtdinboxPage.db.select("SELECT DISTINCT msgid, label FROM auto_labelled WHERE " + where, fRow);
		*/

		var results = this.threadList.gtdinboxPage.sp.getAutoLabelled(msgids);
		for( var i = 0; i < results.length; i++ ){
			var label = results[i][0];
			var msgid = results[i][1];
			for( var j = newlabels[msgid].length - 1; j>=0; j-- ){
				if( newlabels[msgid][j]==label ) newlabels[msgid].splice(j, 1);
			}
		}
	}
		
	var bodylabels = [];	// remove duplicates, note it as seen
	var seen = {};
	for( id in newlabels ){
		for( var i = 0; i < newlabels[id].length; i++ ){
			var label = newlabels[id][i];
			if( !seen[label] ){
				bodylabels.push([label,id]);
				seen[label] = true;
			}
		}
	}	
	return bodylabels;
}catch(e){
	if( GTDDEBUGMODE ) alert("thread getBodyLabels\n" + e.toString());
}
};
Thread.prototype.getUnknownBodyLabels = function(){
	// Return all strings in body that look like labels ([<prefix>...]) and return
	var labelTypes = this.threadList.gtdinboxPage.settings.labels.types;
	var labelsHash = this.threadList.gtdinboxPage._labels.getLabelsHash();
		
	var reStr = "";
	for( t in labelTypes ){
		if( labelTypes[t].prefix ){
			if( reStr.length==0 ) reStr += "|";
			reStr += labelTypes[t].prefix.replace(/([^\w\d\s\t\n])/g, "\\$1");
		}
	}
	var re = new RegExp("\\[(" + reStr + ").+?\\]", "gi");
	var match = this.getMsg().match(re);
	if( match ){
		for( var i = match.length - 1; i >= 0; i-- ){	// remove brackets
			match[i] = match[i].substr(1, match[i].length-2);
			if( labelsHash[match[i]] || match[i].indexOf("mailto")>-1 ) match.splice(i,1);	// label exists, or is an email link
		}
		return match;
	}else return [];
};


Thread.prototype.next = function(){
	var tl = this.threadList._threads;
	for( var i = 0; i < tl.length; i++ ){
		if( tl[i]==this ) return (tl[++i] || null);
	}
};
Thread.prototype.previous = function(){
	var tl = this.threadList._threads;
	for( var i = 0; i < tl.length; i++ ){
		if( tl[i]==this ) return (tl[--i] || null);
	}
};

Thread.prototype.testSubject = function(subject){
	if( !this.subject && /^\s*\(.+\)\s*$/.test(subject) ) return true;	// if no subject, html subject might well be (no subject)
	return this.subject==subject;
};
Thread.prototype.testNames = function(names){
	var environment = this.threadList.gtdinboxPage._environment;
	var threadNames = this.getNames(true);	// only evaluate senders
	var found = true;
	var foundMe = false;
	if( threadNames.length==1 && environment.emails[threadNames[0][1]] ){	// sent mail
		threadNames = this.getNames();	// get recipients too
		for( var j = 0; j < names.length; j++ ){
			var submatch = false;
			for( var k = 0; k < threadNames.length; k++ ){
				if( threadNames[k][1]==names[j][1] ){
					submatch = true;
				}
			}
			if( !submatch ) found = false;
		}
	}else{
		for( var j = 0; j < names.length; j++ ){
			if( environment.emails[names[j][1]] ){
				foundMe = true;
			}else{
				var submatch = false;
				for( var k = 0; k < threadNames.length; k++ ){
					if( threadNames[k][0].toLowerCase().indexOf(names[j][0].toLowerCase())==0 && threadNames[k][1]==names[j][1] ){	//
						submatch = true;
						break;
					}
				}
				if( !submatch ){
					found = false;
					break;
				}
			}
		}
	}
	if( found || (foundMe && names.length==1 && threadNames.length==1) ) return this;
};
Thread.prototype.testSnippet = function(htmlSnippet){
	var snippet = this.rowSnippetHTML || this.getLatestMsg();
	snippet = GIPROTOTYPE.decodeEntities(snippet.replace(/<.+?>/g,""))
	if( !snippet ){
		if( htmlSnippet ) return this;	// if it's an empty message, but with attachments; gmail will show htmlSnippet of attachments; but body still empty
		return this;
	}
	
	var tokens = snippet.match(/(\w{4,})/g);
	snippet = tokens? tokens.join(" ") : "";
	
	var match = true;
	if( htmlSnippet ){
		var htmlSnippetTokens = GIPROTOTYPE.decodeEntities(htmlSnippet.replace(/<[^ ]+>/g,"")).match(/(\w{4,})/g);	
		if( htmlSnippetTokens ){
			var l = (htmlSnippetTokens.length > 5)? 5: htmlSnippetTokens.length;
			for( var i = 0; i < l; i++ ){
				if( snippet.indexOf( htmlSnippetTokens[i] )==-1 ){
					match = false;
					break;
				}
			}
		}else{
			match = false;
		}
	}else{
		if( snippet ) match = false;	// thread has text but row didn't
	}
	
	if( match ) return this;
};

Thread.prototype.testLabels = function(labels, prefixes){
	// Test message body to see if labels can be detected.
	// Explicitly detect presence of labels, then tokenise nesting & remove prefix, and retain any where all tokens match
	
try{
	if( !labels ) return;
	
	if( !prefixes ){
		prefixes = [];
		var ltypes = this.threadList.gtdinboxPage.settings.labels.types;
		for( t in ltypes ){
			prefixes.push(ltypes[t].prefix);
		}
	}
	
	var msg = this.getMsg();
		
	var newlabels = [];
	
	for( var i = 0; i < labels.length; i++ ){
		if( msg.indexOf(labels[i])>-1 ){
			newlabels.push(labels[i]);
		}else{
			var label = labels[i];
			for( var j = 0; j < prefixes.length; j++ ){		// strip prefix
				if( label.indexOf(prefixes[j])==0 ){
					label = label.replace(prefixes[j], "");
					break;
				}
			}
			var tokens = label.split("/");					// tokenise
			var match = true;
			for( var j = 0; j < tokens.length; j++ ){		// match all tokens
				if( msg.indexOf(tokens[j])==-1 ){
					match = false;
					break;
				}
			}
			if( match ) newlabels.push(labels[i]);
		}
	}
	return newlabels;
}catch(e){
	if( GTDDEBUGMODE ) alert("thread testLabels\n" + e.toString());
}
};

Thread.prototype.getMsg = function(excludeQuoted){
	var msg = "";
	for( b in this.body ){
		msg += this.body[b].msg;
	}
	if( excludeQuoted ){
		msg = msg.replace(/\<span(.+?)class\=(\"|\')WQ919c(.+?)\<\/span\>/, "");	// take out 'show quoted' text
		msg = msg.replace(/\<div class\=(\"|\')Wj3C7c.*$/, "");	// take out show quoted text block
	}
	return msg;
};
Thread.prototype.getLatestMsg = function(excludeQuoted){
	var body = null;
	for( b in this.body ){
		if( !body || this.body[b].epoch > body.epoch ) body = this.body[b];
	}
	var msg = body? body.msg : "";
	if( excludeQuoted ){
		msg = msg.replace(/\<span(.+?)class\=(\"|\')WQ919c(.+?)\<\/span\>/, "");	// take out 'show quoted' text
		msg = msg.replace(/\<div class\=(\"|\')Wj3C7c.*$/, "");	// take out show quoted text block
	}
	return msg;
};